#pragma once
//#include "stdafx.h"
#include "Observer.h"
namespace org
{
	namespace spark
	{
		namespace core
		{
			using org::spark::data::DataLayer;
			using org::spark::math::RationalNumber;
			using org::spark::math::SimulationTime;
			using org::spark::space::Space;
			
			

			Observer::ModelSimulationTime::ModelSimulationTime(ModelSimulationTime *time) : SimulationTime(time)
			{
			}

			Observer::ModelSimulationTime::ModelSimulationTime()
			{
			}

			void Observer::ModelSimulationTime::advanceTick()
			{
				SimulationTime::advanceTick();
			}

			void Observer::ModelSimulationTime::setTime(RationalNumber *t)
			{
				SimulationTime::setTime(t);
			}

			void Observer::ModelSimulationTime::reset()
			{
				SimulationTime::reset();
			}
			template<typename T>
			Observer::AgentType::AgentType(T *type, RationalNumber *timeStep, int priority)
			{
				 std::string t=typeid(T).name();
				this->type = type;
				this->timeStep = new RationalNumber(timeStep);
				this->priority = priority;
			}

			int Observer::AgentType::compareTo(AgentType *b)
			{
				// b has higher priority
				if (b->priority < priority)
					return 1;

				// b has lower priority
				if (b->priority > priority)
					return -1;

				// Use lexical ordering
				return type->getName()->compare(b->type->getName());
			}

			Observer::AgentTime::AgentTime(AgentType *type)
			{
				this->type = type;
				this->time = type->timeStep;
			}

			int Observer::AgentTime::compareTo(AgentTime *t)
			{
				return time->compare(t->time);
			}

			void Observer::AgentTime::advanceTime()
			{
				if (type->timeStep->compare(RationalNumber::ZERO) <= 0)
					time->add(RationalNumber::ONE);
				else
					time->add(type->timeStep);
			}

			/*Observer::MyObjectInputStream::MyObjectInputStream(InputStream *in_Renamed) //throw(Exception) : java.io.ObjectInputStream(in)
			{
				InitializeInstanceFields();
			}

			void Observer::MyObjectInputStream::setUserClass(bool flag)
			{
				userClass = flag;
			}

			Class *Observer::MyObjectInputStream::resolveClass(ObjectStreamClass *desc) //throw(IOException, ClassNotFoundException)
			{
				if (cl != 0 && userClass)
				{
					std::string name = desc->getName();
					return cl->loadClass(name);
				}
				else
				{
					return ObjectInputStream::resolveClass(desc);
				}
			}

			void Observer::MyObjectInputStream::InitializeInstanceFields()
			{
				cl = 0;
				userClass = false;
			}*/

//com::spinn3r::log5j::Logger *const Observer::logger = com::spinn3r::log5j::Logger::getLogger();
volatile Observer *Observer::instance = 0;

			bool Observer::getSetupFlag()
			{
				return setupFlag;
			}

			org::spark::math::SimulationTime *Observer::getSimulationTime()
			{
				return new ModelSimulationTime(time);
			}

			RationalNumber *Observer::getTime()
			{
				return time->getTime();
			}

			int Observer::getExecutionMode()
			{
				return executionMode;
			}

			long long Observer::getSimulationTick()
			{
				return time->getTick();
			}

			void Observer::advanceSimulationTick()
			{
				time->advanceTick();
			}

			/*bool Observer::isSerial()
			{
				return setupFlag || (executionMode == ExecutionMode::SERIAL_MODE);
			}*/

			Observer::Observer(Observer *implementation, int executionMode) : agentTypes(std::map<std::string, AgentType*>()), actionQueue(new PriorityQueue<AgentTime*>(20)), time(new ModelSimulationTime()), spacesList(std::vector<Space*>()), spacesMap(std::map<std::string, Space*>()), newAgents(std::map<std::string, std::vector<Agent*>>()), removedQueue(std::vector<Agent*>(100))
			{
				InitializeInstanceFields();
				this->impl = implementation;
				impl->setObserver(this);





						//		controlCommands = new LinkedList<String>();


				/*if (ExecutionMode::isMode(executionMode))
				{
					this->executionMode = executionMode;
				}
				else
				{
					this->executionMode = ExecutionMode::SERIAL_MODE;
				}*/

				//RandomHelper::reset(executionMode == ExecutionMode::PARALLEL_MODE);

// There is no native C++ equivalent to 'toString':
				//logger->debug("Observer is created. Execution mode: " + ExecutionMode->toString(executionMode));
			}

			org::spark::core::Observer *Observer::getInstance()
			{
				return instance;
			}

			org::spark::space::Space *Observer::getSpace()
			{
				return getDefaultSpace();
			}

			org::spark::space::Space *Observer::getDefaultSpace()
			{
				if (instance != 0)
					return instance->defaultSpace;

				return 0;
			}

			org::spark::space::Space *Observer::getSpace(const std::string &name)
			{
				if (instance != 0)
				{
					return instance->spacesMap[name];
				}

				return 0;
			}
			/*
			void Observer::addKeyEvent(ControlState::KeyEvent *keyEvent)
			{
				keyState->changeState(keyEvent);
				keyEvents.push_back(keyEvent);
			}

			void Observer::addMouseEvent(ControlState::MouseEvent *mouseEvent)
			{
				mouseState->changeState(mouseEvent);
				mouseEvents.push_back(mouseEvent);
			}

			ControlState::KeyState *Observer::getKeyState()
			{
				return keyState;
			}

			ControlState::MouseState *Observer::getMouseState()
			{
				return mouseState;
			}

			ControlState::KeyEvent *Observer::nextKeyEvent()
			{
				return keyEvents.pop_front();
			}

			ControlState::MouseEvent *Observer::nextMouseEvent()
			{
				return mouseEvents.pop_front();
			}*/
			/*
			void Observer::reset()
			{
				newAgents.clear();

// WARNING:  converted the original 'null' assignment to a call to 'delete', but you should review memory allocation of all pointer variables in the converted code:
				delete defaultSpace;
				spacesList.clear();
				spacesMap.clear();

				actionQueue->clear();
				time->reset();

						//		controlCommands.clear();
				keyEvents.clear();
				mouseEvents.clear();
				keyState->reset();

				impl->clear();

				RandomHelper::reset(executionMode == ExecutionMode::PARALLEL_MODE);
			}

			void Observer::clearAgents()
			{
				newAgents.clear();
				impl->clearAgents();
			}*/

			void Observer::initializeActionQueue()
			{
				actionQueue->clear();

				for (std::map<Class*, AgentType*>::const_iterator type = agentTypes.begin(); type != agentTypes.end(); ++type)
				{
					AgentTime *t = new AgentTime(type->second);
					t->time = time->getTime()->add(type->second->timeStep);
					actionQueue->add(t);
				}
			}

			void Observer::setAgentType(std::string *type, RationalNumber *timeStep, int priority)
			{
				AgentType *t = agentTypes[type];

				if (priority < HIGH_PRIORITY)
					priority = HIGH_PRIORITY;
				else if (priority > LOW_PRIORITY)
					priority = LOW_PRIORITY;

				if (t == 0)
				{
					t = new AgentType(type, timeStep, priority);
					agentTypes[type] = t;

					AgentTime *tt = new AgentTime(t);
					tt->time = time->getTime()->add(timeStep);

					actionQueue->add(tt);
				}
				else
				{
					t->timeStep = timeStep;
					t->priority = priority;
				}
			}

			void Observer::setAgentType(std::string type)
			{
				std::map<std::string, AgentType*>::iterator itr;
				itr = agentTypes.find(type);
				AgentType *t=itr->second;

				/* if (t == 0)
				{
					t = new AgentType(type, RationalNumber::ONE, LOW_PRIORITY);
					agentTypes[type] = t;

					AgentTime *tt = new AgentTime(t);
					tt->time = time->getTime()->add(RationalNumber::ONE);

					actionQueue->add(tt);
				}*/
			}

			void Observer::setSpace(Space *space)
			{
				addSpace("space", space);
				setDefaultSpace("space");
			}

// There is no native C++ template equivalent to generic constraints:
			template<typename T> 
			T Observer::addSpace(const std::string &name, T space)
			{
				if (spacesMap.find(name) != spacesMap.end())
					//throw Error("Space " + name + " is already defined");

				int index = spacesList.size();
				space->setIndex(index);

				spacesList.push_back(space);
				spacesMap[name] = space;

				if (defaultSpace == 0)
					defaultSpace = space;

				return space;
			}

			std::string *Observer::getSpaceNames()
			{
//ORIGINAL LINE: String[] tmp = new String[0];
// WARNING: Since the array size is not known in this declaration, converted this array to a pointer.   call 'delete[]' where appropriate:
				std::string *tmp = new std::string[0];
				return spacesMap.keySet()->toArray(tmp);
			}

			std::string Observer::getSpaceName(Space *space)
			{
				for (std::map<std::string, Space*>::const_iterator name = spacesMap.begin(); name != spacesMap.end(); ++name)
				{
					if (space == spacesMap[name->first])
						return name->first;
				}

				return 0;
			}

			void Observer::setDefaultSpace(const std::string &name)
			{
				Space *space = spacesMap[name];

				if (space != 0)
					defaultSpace = space;
			}

// There is no native C++ template equivalent to generic constraints:
template<typename T> 
			T Observer::addDataLayer(const std::string &name, T data)
			{
				if (data->getSpace() != defaultSpace)
					//throw Error("A data layer " + name + " is associated with " + data->getSpace() + ", so it cannot be added into " + defaultSpace);

				return defaultSpace->addDataLayer(name, data);
			}

			DataLayer *Observer::getDataLayer(const std::string &name)
			{
				return findDataLayer(name);
			}

			DataLayer *Observer::findDataLayer(const std::string &name)
			{
				for (std::vector<Space*>::const_iterator space = spacesList.begin(); space != spacesList.end(); ++space)
				{
					DataLayer *data = (*space)->getDataLayer(name);

					if (data != 0)
						return data;
				}

				return 0;
			}
			template<typename T>
			void Observer::addAgent(Agent *agent)
			{
				std::string cl = typeid(*agent).name();

				if (setupFlag)
				{
					impl->addAgent(agent, cl);
					return;
				}

				// Serial Mode
				
				impl->addAgent(agent, cl);
				

				// Concurrent Mode
				

				// Parallel Mode
				/*case ExecutionMode::PARALLEL_MODE:
// There is no built-in support for multithreading in native C++:
					synchronized(newAgents)
					{
						list = newAgents[cl];

						if (list.size() > 0)
						{
							list.push_back(agent);
						}
						else
						{
							list = std::vector<Agent*>(100);
							list.push_back(agent);
							newAgents[cl] = list;
						}
					}
					break;*/

				
			}
			void Observer::setObserver(Observer *observer) {
			this->impl = observer;
			}

			void Observer::addAgentInstantly(Agent *agent)
			{
				impl->addAgent(agent);
			}
			template<typename T>
			void Observer::removeAgent(Agent *agent)
			{
				if (setupFlag)
				{
					std::string cl = typeid(agent).name();

					agent->dead = true;

					// TODO: removeAgent(agent, cl)
					if (impl->removeAgent(agent))
						return;

					std::vector<Agent*> list = newAgents[cl];
					if (list.size() > 0)
					{
						list.remove(agent);
					}

					return;
				}

				//switch (executionMode)
				//{
				//case ExecutionMode::SERIAL_MODE:
					std::string cl = typeid(agent).name();

					agent->dead = true;

					// TODO: removeAgent(agent, cl)
					if (impl->removeAgent(agent))
						return;

					std::vector<Agent*> list = newAgents[cl];
					if (list.size() > 0)
					{
						list.remove(agent);
					}
					//break;

				/*case ExecutionMode::CONCURRENT_MODE:
					removedQueue.push_back(agent);
					break;

				case ExecutionMode::PARALLEL_MODE:
// There is no built-in support for multithreading in native C++:
					synchronized(removedQueue)
					{
						removedQueue.push_back(agent);
					}*/
				}
			
			template<typename T>
			int Observer::getAgentsNumber(T *type)
			{
				return impl->getAgentsNumber(type);
			}
			template<typename T>
			int Observer::getAgentsNumberOfKind(T *type)
			{
				return impl->getAgentsNumberOfKind(type);
			}

			void Observer::processNewAgents()
			{
				for (std::map<std::string, std::vector<Agent*>>::const_iterator cl = newAgents.begin(); cl != newAgents.end(); ++cl)
				{
					std::vector<Agent*> newList = newAgents[cl->first];

					impl->addAllAgents(newList, cl->first);
					newList.clear();
				}
			}

			void Observer::processRemovedAgents()
			{
				for (int i = 0; i < removedQueue.size(); i++)
				{
					Agent *agent = removedQueue[i];
					// It is possible that an agent is added to the queue twice
					// Avoid that by checking the dead flag
					if (agent->dead)
						continue;

					agent->dead = true;

					if (impl->removeAgent(agent))
						continue;

					std::vector<Agent*> list = newAgents[agent->getClass()];
					if (list.size() > 0)
					{
						list.remove(agent);
					}
				}

				removedQueue.clear();
			}

			void Observer::processAllAgents(long long tick)
			{
				// TODO: execution mode works here
				impl->processAllAgents(tick);

				long long start = System::currentTimeMillis();
				processRemovedAgents();
				processNewAgents();

				for (std::vector<Space*>::const_iterator space = spacesList.begin(); space != spacesList.end(); ++space)
				{
					(*space)->processNodes();
				}

				long long end = System::currentTimeMillis();

				statTime += end - start;
			}

			void Observer::fillTypeList(RationalNumber *t1)
			{
				tempTypeList.clear();

				// Take the first time event from the queue
				AgentTime *t = actionQueue->peek();

				// No time events
				if (t == 0)
				{
					time->setTime(t1);
					return;
				}

				RationalNumber *t0 = new RationalNumber(t->time);

				// It's not time yet
				if (t0->compare(t1) > 0)
				{
					time->setTime(t1);
					return;
				}

				time->setTime(t0);

				// Add the first type into the list
				tempTypeList.push_back(t->type);

				// Advance time for the first time event and return it to the queue
				actionQueue->poll();
				t->advanceTime();
				actionQueue->add(t);

				// Get time events with the same time
				while (true)
				{
					t = actionQueue->peek();

					if (t == 0)
						break;

					int c = t->time->compare(t0);

					if (c > 0)
					{
						break;
					}

					/*if (c < 0)
					{
						//throw Error("Error in action queue");
					}*/

					if (c == 0)
					{
						t = actionQueue->poll();
						tempTypeList.push_back(t->type);

						t->advanceTime();
						actionQueue->add(t);
					}
				}

			}

			void Observer::processAllAgents(RationalNumber *dt)
			{
				RationalNumber *t1 = time->getTime()->add(dt);

				while (true)
				{
					fillTypeList(t1);

					if (tempTypeList.empty())
					{
						break;
					}

					AgentType types[tempTypeList.size()];
					types = tempTypeList.toArray(types);

					// Sort types according to their priorities
					Arrays::sort(types);

					// Begin step for all data layers
					for (std::vector<Space*>::const_iterator space = spacesList.begin(); space != spacesList.end(); ++space)
					{
						(*space)->dataLayersBeginStep();
					}

					// Process all agents
					for (int i = 0; i < sizeof(types) / sizeof(types[0]); i++)
					{
						impl->processAgents(types[i]->type, time);
					}

					// Post process for agents
					processRemovedAgents();
					processNewAgents();

					for (std::vector<Space*>::const_iterator space = spacesList.begin(); space != spacesList.end(); ++space)
					{
						(*space)->processNodes();
					}

					// End step for all data layers
					for (std::vector<Space*>::const_iterator space = spacesList.begin(); space != spacesList.end(); ++space)
					{
						(*space)->dataLayersEndStep();
					}
				}

				// TODO:
				// Ignore static agents
				// Make steps and collect statistics for each agent type separately
			}

			void Observer::beginSetup()
			{
				time->reset();
				setupFlag = true;
			}

			void Observer::finalizeSetup()
			{
				// TODO: next line are not required
				// because setup always works in the serial mode
				//processRemovedAgents();
				//processNewAgents();

				for (std::vector<Space*>::const_iterator space = spacesList.begin(); space != spacesList.end(); ++space)
				{
					(*space)->processNodes();
				}

				time->reset();
				initializeActionQueue();

				setupFlag = false;
			}

			void Observer::processAllDataLayers(long long tick)
			{
				// TODO: execution modes
				for (std::vector<Space*>::const_iterator space = spacesList.begin(); space != spacesList.end(); ++space)
				{
					(*space)->processAllDataLayers(tick);
				}
			}

// There is no native C++ template equivalent to generic constraints:
template<typename T>
			T *Observer::getAgents(Class *type)
			{
				return impl->getAgents(type);
			}

			Agent *Observer::getAgents()
			{
				return impl->getAgents();
			}

// There is no native C++ template equivalent to generic constraints:
template<typename T>
			std::vector<T*> Observer::getAgentsList(T *type)
			{
				return impl->getAgentsList(type);
			}

// There is no native C++ template equivalent to generic constraints:
template<typename T> 
			std::vector<T> Observer::getAgentsListOfKind(Class *type)
			{
				return impl->getAgentsListOfKind(type);
			}

			std::string *Observer::getDataLayers()
			{
				std::vector<std::string> names = std::vector<std::string>();
				//ORIGINAL LINE: String[] tmp = new String[0];
				// WARNING: Since the array size is not known in this declaration, converted this array to a pointer.   call 'delete[]' where appropriate:
				std::string *tmp = new std::string[0];

				for (std::vector<Space*>::const_iterator space = spacesList.begin(); space != spacesList.end(); ++space)
				{
					tmp = (*space)->getDataLayerNames();
					if (tmp == 0)
						continue;

					for (int i = 0; i < tmp->length; i++)
						names.push_back(tmp[i]);
				}

				return names.toArray(tmp);
			}

			void Observer::printStatistics()
			{
				logger->debug("Additional time: %d", statTime);
				System::err::println("Additional time: " + statTime);
				impl->printStatistics();
			}

			/*void Observer::serializeState(SparkModel *model, OutputStream *out) //throw(Exception)
			{
				// TODO: save simulation time
				//if (newAgents.size() != 0 || removedQueue.size() != 0)
					//throw Exception("Could not serialize model during agent's processing");

				// Create an object output stream
				//ObjectOutputStream *oos = new ObjectOutputStream(out);

				// Static model variables
//ORIGINAL LINE: Field[] fields = model.getClass().getFields();
// WARNING: Since the array size is not known in this declaration, converted this array to a pointer.   call 'delete[]' where appropriate:
				/*Field *fields = model->getClass()->getFields();
				if (fields == 0)
					fields = new Field[0];

				oos->writeInt(fields->length);
				for (Field::const_iterator field = fields->begin(); field != fields->end(); ++field)
				{
					if (Modifier::isStatic(field->getModifiers()))
					{
						Object *val = field->get(model);

						// TODO: val should be primitive or String

						oos->writeObject(field->getName());
						oos->writeObject(val);
					}
					else
					{
						//throw Exception("Only static fields are allowed in a model class");
					}
				}

				// serialMode
				oos->writeInt(executionMode);

				// spacesList
				// spacesMap
				std::string defaultSpaceName = "";

				//oos->writeInt(spacesMap.keySet()->size());

				for (std::map<std::string, Space*>::const_iterator spaceName = spacesMap.begin(); spaceName != spacesMap.end(); ++spaceName)
				{
					Space *space = spacesMap[spaceName->first];

					if (space == defaultSpace)
						defaultSpaceName = spaceName->first;

					//oos->writeObject(spaceName->first);
					//space->serializeSpace(oos);
				}

				// defaultSpace
				//oos->writeObject(defaultSpaceName);

				// Agents
				impl->serializeAgents(oos);
			}*/
			
			/*void Observer::loadState(SparkModel *model, InputStream *in_Renamed, ClassLoader *cl) //throw(Exception)
			{
				// TODO: load simulation time
				// TODO: save/load states on a specific thread only
				//throw Error("Not implemented");
						/*
								if (newAgents.size() != 0 || removedQueue.size() != 0)
									//throw new Exception(
											"Could not load model state during agent's processing");
						
								// Clear everything
								clear();
						
								// Create an object input stream
								MyObjectInputStream ois = new MyObjectInputStream(in);
								ois.cl = cl;
						
								// Read global parameters
								int n = ois.readInt();
						
								for (int i = 0; i < n; i++) {
									String fieldName = (String) ois.readObject();
									Object value = ois.readObject();
									Field field = model.getClass().getField(fieldName);
						
									if (!Modifier.isFinal(field.getModifiers()))
										model.getClass().getField(fieldName).set(model, value);
								}
						
								// serialMode
						//		setExecutionMode(ois.readInt());
						
								// spacesList
								// spacesMap
								n = ois.readInt();
						
								for (int i = 0; i < n; i++) {
									String spaceName = (String) ois.readObject();
									Space space = Space.loadSpace(ois);
									if (space == null)
										//throw new Exception("Space " + spaceName
												+ " could not be loaded");
						
									addSpace(spaceName, space);
								}
						
								String defaultSpaceName = (String) ois.readObject();
								setDefaultSpace(defaultSpaceName);
						
								// Agents
								ois.setUserClass(true);
								impl.loadAgents(ois);
			}*/
			

			void Observer::InitializeInstanceFields()
			{
				tempTypeList = std::vector<AgentType*>();
			}
		}
	}
}
